/** @file
  I2C Bus implementation upon CirrusLogic.

  Copyright (c) 2008 - 2009, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "CirrusLogic5430.h"
#include "CirrusLogic5430I2c.h"

#define SEQ_ADDRESS_REGISTER    0x3c4
#define SEQ_DATA_REGISTER       0x3c5

#define I2C_CONTROL             0x08
#define I2CDAT_IN               7
#define I2CCLK_IN               2
#define I2CDAT_OUT              1
#define I2CCLK_OUT              0

#define I2C_BUS_SPEED           100  //100kbps

/**
  PCI I/O byte write function.

  @param  PciIo        The pointer to PCI_IO_PROTOCOL.
  @param  Address      The bit map of I2C Data or I2C Clock pins.
  @param  Data         The date to write.

**/
VOID
I2cOutb (
  EFI_PCI_IO_PROTOCOL    *PciIo,
  UINTN                  Address,
  UINT8                  Data
  )
{
  PciIo->Io.Write (
             PciIo,
             EfiPciIoWidthUint8,
             EFI_PCI_IO_PASS_THROUGH_BAR,
             Address,
             1,
             &Data
             );
}
/**
  PCI I/O byte read function.

  @param  PciIo        The pointer to PCI_IO_PROTOCOL.
  @param  Address      The bit map of I2C Data or I2C Clock pins.

  return byte value read from PCI I/O space.

**/
UINT8
I2cInb (
  EFI_PCI_IO_PROTOCOL    *PciIo,
  UINTN                  Address
  )
{
  UINT8 Data;

  PciIo->Io.Read (
             PciIo,
             EfiPciIoWidthUint8,
             EFI_PCI_IO_PASS_THROUGH_BAR,
             Address,
             1,
             &Data
             );
  return Data;
}

/**
  Read status of I2C Data and I2C Clock Pins.

  @param  PciIo        The pointer to PCI_IO_PROTOCOL.
  @param  Blt          The bit map of I2C Data or I2C Clock pins.

  @retval 0            Low on I2C Data or I2C Clock Pin.
  @retval 1            High on I2C Data or I2C Clock Pin.

**/
UINT8
I2cPinRead (
  EFI_PCI_IO_PROTOCOL    *PciIo,
  UINT8                  Bit
  )
{
  I2cOutb (PciIo, SEQ_ADDRESS_REGISTER, I2C_CONTROL);
  return (UINT8) ((I2cInb (PciIo, SEQ_DATA_REGISTER) >> Bit ) & 0xfe);
}


/**
  Set/Clear I2C Data and I2C Clock Pins.

  @param  PciIo              The pointer to PCI_IO_PROTOCOL.
  @param  Blt                The bit map to controller I2C Data or I2C Clock pins.
  @param  Value              1 or 0 stands for Set or Clear I2C Data and I2C Clock Pins.

**/
VOID
I2cPinWrite (
  EFI_PCI_IO_PROTOCOL    *PciIo,
  UINT8                  Bit,
  UINT8                  Value
  )
{
  UINT8        Byte;
  I2cOutb (PciIo, SEQ_ADDRESS_REGISTER, I2C_CONTROL);
  Byte = (UINT8) (I2cInb (PciIo, SEQ_DATA_REGISTER) & (UINT8) ~(1 << Bit)) ;
  Byte = (UINT8) (Byte | ((Value & 0x01) << Bit));
  I2cOutb (PciIo, SEQ_DATA_REGISTER, (UINT8) (Byte | 0x40));
  return;
}

/**
  Read/write delay acoording to I2C Bus Speed.

**/
VOID
I2cDelay (
  VOID
  )
{
  MicroSecondDelay (1000 / I2C_BUS_SPEED);
}

/**
  Write a 8-bit data onto I2C Data Pin.

  @param  PciIo              The pointer to PCI_IO_PROTOCOL.
  @param  Data               The byte data to write.

**/
VOID
I2cSendByte (
  EFI_PCI_IO_PROTOCOL    *PciIo,
  UINT8                  Data
  )
{
  UINTN                  Index;
  //
  // Send byte data onto I2C Bus
  //
  for (Index = 0; Index < 8; Index --) {
    I2cPinWrite (PciIo, I2CDAT_OUT, (UINT8) (Data >> (7 - Index)));
    I2cPinWrite (PciIo, I2CCLK_OUT, 1);
    I2cDelay ();
    I2cPinWrite (PciIo, I2CCLK_OUT, 0);
  }
}

/**
  Read a 8-bit data from I2C Data Pin.

  @param  PciIo              The pointer to PCI_IO_PROTOCOL.

  Return the byte data read from I2C Data Pin.
**/
UINT8
I2cReceiveByte (
  EFI_PCI_IO_PROTOCOL    *PciIo
  )
{
  UINT8          Data;
  UINTN          Index;

  Data = 0;
  //
  // Read byte data from I2C Bus
  //
  for (Index = 0; Index < 8; Index --) {
    I2cPinWrite (PciIo, I2CCLK_OUT, 1);
    I2cDelay ();
    Data = (UINT8) (Data << 1);
    Data = (UINT8) (Data | I2cPinRead (PciIo, I2CDAT_IN));
    I2cPinWrite (PciIo, I2CCLK_OUT, 0);
  }

  return Data;
}

/**
  Receive an ACK signal from I2C Bus.

  @param  PciIo              The pointer to PCI_IO_PROTOCOL.

**/
BOOLEAN
I2cWaitAck (
  EFI_PCI_IO_PROTOCOL    *PciIo
  )
{
  //
  // Wait for ACK signal
  //
  I2cPinWrite (PciIo, I2CDAT_OUT, 1);
  I2cPinWrite (PciIo, I2CCLK_OUT, 1);
  I2cDelay ();
  if (I2cPinRead (PciIo, I2CDAT_IN) == 0) {
    I2cPinWrite (PciIo, I2CDAT_OUT, 1);
    return TRUE;
  } else {
    return FALSE;
  }
}

/**
  Send an ACK signal onto I2C Bus.

  @param  PciIo              The pointer to PCI_IO_PROTOCOL.

**/
VOID
I2cSendAck (
  EFI_PCI_IO_PROTOCOL    *PciIo
  )
{
  I2cPinWrite (PciIo, I2CCLK_OUT, 1);
  I2cPinWrite (PciIo, I2CDAT_OUT, 1);
  I2cPinWrite (PciIo, I2CDAT_OUT, 0);
  I2cPinWrite (PciIo, I2CCLK_OUT, 0);
}

/**
  Start a I2C transfer on I2C Bus.

  @param  PciIo              The pointer to PCI_IO_PROTOCOL.

**/
VOID
I2cStart (
  EFI_PCI_IO_PROTOCOL    *PciIo
  )
{
  //
  // Init CLK and DAT pins
  //
  I2cPinWrite (PciIo, I2CCLK_OUT, 1);
  I2cPinWrite (PciIo, I2CDAT_OUT, 1);
  //
  // Start a I2C transfer, set SDA low from high, when SCL is high
  //
  I2cPinWrite (PciIo, I2CDAT_OUT, 0);
  I2cPinWrite (PciIo, I2CCLK_OUT, 0);
}

/**
  Stop a I2C transfer on I2C Bus.

  @param  PciIo              The pointer to PCI_IO_PROTOCOL.

**/
VOID
I2cStop (
  EFI_PCI_IO_PROTOCOL    *PciIo
  )
{
  //
  // Stop a I2C transfer, set SDA high from low, when SCL is high
  //
  I2cPinWrite (PciIo, I2CDAT_OUT, 0);
  I2cPinWrite (PciIo, I2CCLK_OUT, 1);
  I2cPinWrite (PciIo, I2CDAT_OUT, 1);
}

/**
  Read one byte data on I2C Bus.

  Read one byte data from the slave device connectet to I2C Bus.
  If Data is NULL, then ASSERT().

  @param  PciIo              The pointer to PCI_IO_PROTOCOL.
  @param  DeviceAddress      Slave device's address.
  @param  RegisterAddress    The register address on slave device.
  @param  Data               The pointer to returned data if EFI_SUCCESS returned.

  @retval EFI_DEVICE_ERROR
  @retval EFI_SUCCESS

**/
EFI_STATUS
EFIAPI
I2cReadByte (
  EFI_PCI_IO_PROTOCOL    *PciIo,
  UINT8                  DeviceAddress,
  UINT8                  RegisterAddress,
  UINT8                  *Data
  )
{
  ASSERT (Data != NULL);

  //
  // Start I2C transfer
  //
  I2cStart (PciIo);

  //
  // Send slave address with enabling write flag
  //
  I2cSendByte (PciIo, (UINT8) (DeviceAddress & 0xfe));

  //
  // Wait for ACK signal
  //
  if (I2cWaitAck (PciIo) == FALSE) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Send register address
  //
  I2cSendByte (PciIo, RegisterAddress);

  //
  // Wait for ACK signal
  //
  if (I2cWaitAck (PciIo) == FALSE) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Send slave address with enabling read flag
  //
  I2cSendByte (PciIo, (UINT8) (DeviceAddress | 0x01));

  //
  // Wait for ACK signal
  //
  if (I2cWaitAck (PciIo) == FALSE) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Read byte data from I2C Bus
  //
  *Data = I2cReceiveByte (PciIo);

  //
  // Send ACK signal onto I2C Bus
  //
  I2cSendAck (PciIo);

  //
  // Stop a I2C transfer
  //
  I2cStop (PciIo);

  return EFI_SUCCESS;
}

/**
  Write one byte data onto I2C Bus.

  Write one byte data to the slave device connectet to I2C Bus.
  If Data is NULL, then ASSERT().

  @param  PciIo              The pointer to PCI_IO_PROTOCOL.
  @param  DeviceAddress      Slave device's address.
  @param  RegisterAddress    The register address on slave device.
  @param  Data               The pointer to write data.

  @retval EFI_DEVICE_ERROR
  @retval EFI_SUCCESS

**/
EFI_STATUS
EFIAPI
I2cWriteByte (
  EFI_PCI_IO_PROTOCOL    *PciIo,
  UINT8                  DeviceAddress,
  UINT8                  RegisterAddress,
  UINT8                  *Data
  )
{
  ASSERT (Data != NULL);

  I2cStart (PciIo);
  //
  // Send slave address with enabling write flag
  //
  I2cSendByte (PciIo, (UINT8) (DeviceAddress & 0xfe));

  //
  // Wait for ACK signal
  //
  if (I2cWaitAck (PciIo) == FALSE) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Send register address
  //
  I2cSendByte (PciIo, RegisterAddress);

  //
  // Wait for ACK signal
  //
  if (I2cWaitAck (PciIo) == FALSE) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Send byte data onto I2C Bus
  //
  I2cSendByte (PciIo, *Data);

  //
  // Wait for ACK signal
  //
  if (I2cWaitAck (PciIo) == FALSE) {
    return EFI_DEVICE_ERROR;
  }

  //
  // Stop a I2C transfer
  //
  I2cStop (PciIo);

  return EFI_SUCCESS;
}



